/*
 * Tranquil Java Integrated Development Environment
 *
 * The GNU General Public License Version 3
 *
 * Copyright (C) 2021 Autumn Lamonte
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Autumn Lamonte [AutumnWalksTheLake@gmail.com] ⚧ Trans Liberation Now
 * @version 1
 */
package tjide.ui;

import java.util.ArrayList;
import java.util.List;
import java.text.MessageFormat;
import java.util.ResourceBundle;

import gjexer.TAction;
import gjexer.TApplication;
import gjexer.TButton;
import gjexer.TInputBox;
import gjexer.TList;
import gjexer.TWidget;
import gjexer.TWindow;
import gjexer.bits.CellAttributes;
import gjexer.event.TKeypressEvent;
import gjexer.layout.StretchLayoutManager;
import gjexer.ttree.TTreeViewWidget;
import static gjexer.TCommand.*;
import static gjexer.TKeypress.*;

import tjide.debugger.DebugValue;
import tjide.debugger.Scope;
import tjide.project.Project;

/**
 * LocalsWindow is a permanent window that provide a variables browser.
 */
public class LocalsWindow extends TWindow {

    /**
     * Translated strings.
     */
    private static ResourceBundle i18n = ResourceBundle.getBundle(LocalsWindow.class.getName());

    // ------------------------------------------------------------------------
    // Constants --------------------------------------------------------------
    // ------------------------------------------------------------------------

    // ------------------------------------------------------------------------
    // Variables --------------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * The selected variable value.
     */
    private DebugValue value;

    /**
     * The variables treeview.
     */
    private TTreeViewWidget variables;

    /**
     * The variables treeview root.
     */
    private LocalsWindowTreeItem variablesRoot;

    /**
     * The modify value button.
     */
    private TButton modifyButton;

    /**
     * The callstack list.
     */
    private TList callstack;

    /**
     * The current scope.
     */
    private Scope scope;

    /**
     * The list of scopes at this execution location.
     */
    private List<Scope> scopes;

    /**
     * The current location within scopes.
     */
    private int scopesI;

    // ------------------------------------------------------------------------
    // Constructors -----------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Construct window.
     *
     * @param parent the main application
     */
    public LocalsWindow(final TApplication parent) {
        super(parent, i18n.getString("windowTitle"), 0, 1, 70, 22,
            CENTERED | RESIZABLE | HIDEONCLOSE);

        setLayoutManager(new StretchLayoutManager(getWidth() - 2,
                getHeight() - 2));

        final int buttonOffset = 14;

        statusBar = newStatusBar(i18n.getString("statusBar"));
        statusBar.addShortcutKeypress(kbF1, cmHelp,
            i18n.getString("statusBarHelp"));
        statusBar.addShortcutKeypress(kbF10, cmMenu,
            i18n.getString("statusBarMenu"));

        // Variables
        addLabel(i18n.getString("variables"), 2, 1,
            new TAction() {
                public void DO() {
                    LocalsWindow.this.activate(variables);
                }
            });
        variables = addTreeViewWidget(2, 2, getWidth() - buttonOffset - 5, 11,
            new TAction() {
                public void DO() {
                    copyValue();
                }
            });
        variablesRoot = new LocalsWindowTreeItem(variables);

        // Callstack
        addLabel(i18n.getString("callstack"), 2, 14);
        callstack = addList(new ArrayList<String>(), 2, 15,
            getWidth() - buttonOffset - 5, 4);

        // Buttons
        modifyButton = addButton(i18n.getString("modifyButton"),
            getWidth() - buttonOffset, 2,
            new TAction() {
                public void DO() {
                    modifyValue();
                }
            });
        modifyButton.setEnabled(false);

        addButton(i18n.getString("upButton"),
            getWidth() - buttonOffset, 4,
            new TAction() {
                public void DO() {
                    upStack();
                }
            });

        addButton(i18n.getString("downButton"),
            getWidth() - buttonOffset, 6,
            new TAction() {
                public void DO() {
                    downStack();
                }
            });

        addButton(i18n.getString("closeButton"),
            getWidth() - buttonOffset, 8,
            new TAction() {
                public void DO() {
                    // Note that we hide, not close: this is a single
                    // persistent window.
                    LocalsWindow.this.hide();
                }
            });

        // Save this for last: make the variables tree the default selected.
        activate(variables);

        hide();
    }

    // ------------------------------------------------------------------------
    // Event handlers ---------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Handle keystrokes.
     *
     * @param keypress keystroke event
     */
    @Override
    public void onKeypress(final TKeypressEvent keypress) {
        // Handle the keystroke, and hang on to whatever the selected value
        // is.
        super.onKeypress(keypress);
        copyValue();
    }

    // ------------------------------------------------------------------------
    // TWindow ----------------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Retrieve the background color.
     *
     * @return the background color
     */
    @Override
    public final CellAttributes getBackground() {
        return getTheme().getColor("localsWindow.background");
    }

    /**
     * Retrieve the border color.
     *
     * @return the border color
     */
    @Override
    public CellAttributes getBorder() {
        if (inWindowMove) {
            return getTheme().getColor("localsWindow.windowMove");
        }
        return getTheme().getColor("localsWindow.background");
    }

    /**
     * Retrieve the color used by the window movement/sizing controls.
     *
     * @return the color used by the zoom box, resize bar, and close box
     */
    @Override
    public CellAttributes getBorderControls() {
        return getTheme().getColor("localsWindow.borderControls");
    }

    // ------------------------------------------------------------------------
    // LocalsWindow -----------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Go up the stack.
     */
    private void upStack() {
        if (scope == null) {
            return;
        }
        if (scopesI == scopes.size() - 1) {
            return;
        }
        scopesI++;
        scope = scopes.get(scopesI);
        updateFromScope();
    }

    /**
     * Go down the stack.
     */
    private void downStack() {
        if (scope == null) {
            return;
        }
        if (scopesI == 0) {
            return;
        }
        scopesI--;
        scope = scopes.get(scopesI);
        updateFromScope();
    }

    /**
     * Modify the selected value in the treeview.  Note package private
     * access.
     */
    void modifyValue() {
        assert (modifyButton.isEnabled());
        assert (value != null);
        assert (value.canModify());

        TInputBox inputBox = getApplication().inputBox(i18n.
            getString("modifyInputBoxTitle"),
            MessageFormat.format(i18n.getString("modifyInputBoxCaption"),
                value.getName()),
            value.getValue(),
            TInputBox.Type.OKCANCEL);

        if (inputBox.getResult() == TInputBox.Result.OK) {
            value.setValue(inputBox.getText());
            ((LocalsWindowTreeItem) variables.getSelected()).updateValue();
        }
    }

    /**
     * Copy the selected value in the treeview to value, if it is not a
     * compound value type.
     */
    private void copyValue() {
        LocalsWindowTreeItem item;
        item = (LocalsWindowTreeItem) variables.getSelected();
        if (item != null) {
            value = item.getValue();
        } else {
            value = null;
        }
        if (value != null) {
            modifyButton.setEnabled(value.canModify());
        } else {
            modifyButton.setEnabled(false);
        }
    }

    /**
     * Update locals window to reflect values at the current debug execution
     * location.
     */
    public void updateLocals() {
        TranquilApplication app = (TranquilApplication) getApplication();
        scope = app.getDebugScope();
        scopes = new ArrayList<Scope>();
        scopesI = 0;
        if (scope != null) {
            for (Scope s = scope; s != null; s = s.getParentScope()) {
                scopes.add(s);
            }
        }

        updateFromScope();
    }

    /**
     * Update locals window to reflect values at a higher or lower scope.
     */
    private void updateFromScope() {
        if (scope == null) {
            callstack.setList(new ArrayList<String>());
            variablesRoot.clear();
        } else {
            callstack.setList(scope.getAllCallstackStrings());
            variablesRoot.setScope(scope);
        }
    }

}
